/*
 * This file is part of aion-engine <aion-engine.com>
 *
 * aion-engine is private software: you can redistribute it and or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Private Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aion-engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with aion-engine.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionengine.gameserver.services.toypet;

import com.aionemu.commons.utils.Rnd;
import com.aionengine.gameserver.dataholders.DataManager;
import com.aionengine.gameserver.model.templates.pet.PetFeedResult;
import com.aionengine.gameserver.model.templates.pet.PetFlavour;
import com.aionengine.gameserver.model.templates.pet.PetRewards;
import org.apache.commons.lang.ArrayUtils;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

/**
 * @author Rolandas
 */

/**
 * <b>Current pre-calculated values multiplied by 4; in packet 14 bits.
 * Max value: 17600 / 4 is 13 bits; feed points as in retail packets.</b><br>
 * static final byte[][] pointValues = new byte[][] {<br>
 * //  10    25    40    50   100    200 -- feed max count<br>
 * {    0,    0,    0,    0,    0,     0 }, // level   1~5 items (feed points  0)<br>
 * {   80,  200,  320,  400,  800,  1600 }, // level  6~10 items (feed points  8)<br>
 * {  160,  400,  640,  800, 1600,  3200 }, // level 11~15 items (feed points 16)<br>
 * {  240,  600,  960, 1200, 2400,  4800 }, // level 16~20 items (feed points 24)<br>
 * {  320,  800, 1280, 1600, 3200,  6400 }, // level 21~25 items (feed points 32)<br>
 * {  400, 1000, 1600, 2000, 4000,  8000 }, // level 26~30 items (feed points 40)<br>
 * {  480, 1200, 1920, 2400, 4800,  9600 }, // level 31~35 items (feed points 48)<br>
 * {  560, 1400, 2240, 2800, 5600, 11200 }, // level 36~40 items (feed points 56)<br>
 * {  640, 1600, 2560, 3200, 6400, 12800 }, // level 41~45 items (feed points 64)<br>
 * {  720, 1800, 2880, 3600, 7200, 14400 }, // level 46~50 items (feed points 72)<br>
 * {  800, 2000, 3200, 4000, 8000, 16000 }, // level 51~55 items (feed points 80)<br>
 * {  880, 2200, 3520, 4400, 8800, 17600 }  // level 56~60 items (feed points 88)<br>
 * };
 */
public final class PetFeedCalculator {

    static byte ITEM_MAX_LEVEL = 60;
    static final short[] fullCounts;
    static final byte[] itemLevels;
    static final int[][] pointValues;

    static {
        TreeSet<Short> counts = new TreeSet<Short>();
        for (PetFlavour flavour : DataManager.PET_FEED_DATA.getPetFlavours()) {
            if (flavour.getFullCount() > 0)
                counts.add((short) (flavour.getFullCount() & 0xFFFF));
        }

        fullCounts = new short[counts.size()];
        int i = 0;
        Iterator<Short> countIter = counts.iterator();
        while (countIter.hasNext())
            fullCounts[i++] = countIter.next();

        itemLevels = new byte[ITEM_MAX_LEVEL / 5];
        itemLevels[0] = 5;
        for (int j = 1; j < itemLevels.length; j++)
            itemLevels[j] = (byte) (itemLevels[j - 1] + 5);

        pointValues = new int[itemLevels.length][fullCounts.length];
        calculate();
    }

    /**
     * Calculate point values for each item levels and each max feed count
     */
    static void calculate() {
        for (byte levelByte : itemLevels) {
            short level = (short) (levelByte & 0xFF);
            if (level < 10)
                continue;
            int countIndex = 0;
            for (short countByte : fullCounts) {
                short count = (short) (countByte & 0xFF);
                int finalLevel = level;
                if (finalLevel % 5 == 0)
                    finalLevel--;
                int pointLevel = (int) itemLevels[(int) (finalLevel / 5)];
                int feedPoints = Math.max(0, pointLevel - 5) / 5 * 8;
                // System.out.println("ITEM LEVEL: " + level + ", COUNT: " + count + ", STEP: " + feedPoints);
                pointValues[finalLevel / 5][countIndex++] = getPoints(feedPoints, count);
            }
        }
    }

    /**
     * Formula to calculate pointValues array
     *
     * @param feedPoints   - feed points for item
     * @param maxFeedCount - max feeding count
     * @return byte increment count after all items are fed
     */
    static int getPoints(int feedPoints, int maxFeedCount) {
        int points = 0;
        int state = 0;
        int consumed = 0;
        while (consumed < maxFeedCount) {
            boolean needSwitch = false;
            int oldPoints = points;
            if ((state == 0 && consumed > maxFeedCount * 0.5f) || (state == 1 && consumed > maxFeedCount * 0.8f)
                    || (state == 2 && consumed > maxFeedCount * 1.05)) {
                needSwitch = true;
            }
            points += feedPoints;
            if (needSwitch) {
                state++;
                if (state == 1 && consumed <= 0.487f * maxFeedCount || state == 2 && consumed <= 0.78f * maxFeedCount) {
                    state--;
                    points = oldPoints;
                }
            }
            consumed++;
        }
        return points;
    }

    public static void updatePetFeedProgress(@Nonnull PetFeedProgress progress, int itemLevel, int maxFeedCount) {
        PetHungryLevel currHungryLevel = progress.getHungryLevel();
        if (progress.isLovedFeeded()) { // loved food
            if (progress.getLovedFoodRemaining() == 0)
                return;
            progress.setHungryLevel(PetHungryLevel.FULL);
            progress.incrementCount(true);
            return;
        }

        int oldPoints = progress.getTotalPoints();
        boolean needSwitch = false;

        if ((currHungryLevel == PetHungryLevel.HUNGRY && progress.getRegularCount() > maxFeedCount * 0.5f)
                || (currHungryLevel == PetHungryLevel.CONTENT && progress.getRegularCount() > maxFeedCount * 0.8f)
                || (currHungryLevel == PetHungryLevel.SEMIFULL && progress.getRegularCount() > maxFeedCount * 1.05)) {
            // forcefully switch level
            needSwitch = true;
        } else {
            int finalLevel = itemLevel;
            if (finalLevel % 5 == 0)
                finalLevel--;

            byte pointLevel = itemLevels[(int) (finalLevel / 5)];
            byte pointsEarned = (byte) (Math.max(0, pointLevel - 5) / 5 * 8);
            int feedProgress = progress.getTotalPoints() + pointsEarned;
            progress.setTotalPoints(feedProgress);
        }

        if (needSwitch) {
            // just a prevention to not switch level
            PetHungryLevel nextLevel = progress.getHungryLevel().getNextValue();
            if (nextLevel == PetHungryLevel.CONTENT && progress.getRegularCount() <= 0.487f * maxFeedCount
                    || nextLevel == PetHungryLevel.SEMIFULL && progress.getRegularCount() <= 0.78f * maxFeedCount) {
                progress.setTotalPoints(oldPoints);
            } else {
                progress.setHungryLevel(nextLevel);
            }
        }
        progress.incrementCount(false);
    }

    public static PetFeedResult getReward(int fullCount, PetRewards rewardGroup, PetFeedProgress progress, int playerLevel) {
        if (progress.getHungryLevel() != PetHungryLevel.FULL || rewardGroup.getResults().size() == 0)
            return null;

        int pointsIndex = ArrayUtils.indexOf(fullCounts, (short) fullCount);
        if (pointsIndex == ArrayUtils.INDEX_NOT_FOUND)
            return null;

        if (progress.isLovedFeeded()) { // for cash feed
            if (rewardGroup.getResults().size() == 1)
                return rewardGroup.getResults().get(0);
            List<PetFeedResult> validRewards = new ArrayList<PetFeedResult>();
            int maxLevel = 0;
            for (PetFeedResult result : rewardGroup.getResults()) {
                int resultLevel = DataManager.ITEM_DATA.getItemTemplate(result.getItem()).getLevel();
                if (resultLevel > playerLevel)
                    continue;
                if (resultLevel > maxLevel) {
                    maxLevel = resultLevel;
                    validRewards.clear();
                }
                validRewards.add(result);
            }
            if (validRewards.size() == 0)
                return null;
            if (validRewards.size() == 1)
                return validRewards.get(0);
            return validRewards.get(Rnd.get(validRewards.size()));
        }

        int rewardIndex = 0;
        int totalRewards = rewardGroup.getResults().size();
        for (int row = 1; row < pointValues.length; row++) {
            int[] points = pointValues[row];
            if (points[pointsIndex] <= progress.getTotalPoints()) {
                rewardIndex = Math.round((float) totalRewards / (pointValues.length - 1) * row) - 1;
            }
        }

        // Fix rounding discrepancy
        if (rewardIndex < 0)
            rewardIndex = 0;
        else if (rewardIndex > rewardGroup.getResults().size() - 1)
            rewardIndex = rewardGroup.getResults().size() - 1;

        return rewardGroup.getResults().get(rewardIndex);
    }
}
